package vg.modules.search.components.searchTreePanel;

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.Set;

import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;

import vg.modules.search.SearchPluginGlobals;
import vg.modules.search.components.FBSecondPanel;
import vg.services.data_base_manager.data.skeleton.GraphTreeElement;
import vg.services.plugin_manager.realization.PluginParameter;
import vg.services.user_interface_manager.additional_swing_components.Locker;

/**
 * This class realizes tree with different functions.
 * @author tzolotuhin
 */
public class SearchTree {
	// Swing components
	private final JPanel view;
	private final JTree tree;
	private final JScrollPane scrollPane;
	private final SearchTreeRenderer treeRenderer;
	private final Locker locker;
	// Main Data
	private GraphTreeElement srcNode = null; // source tree
	private GraphTreeElement currNode = null; // current tree
	private int browseMode = SearchPluginGlobals.DEF_BROWSE_MODE_1;
	private Set<Integer>array; // array of allowed nodes.
	// Mutex
	private final Object theMutexObject = new Object();
	/**
	 * Constructor.
	 * @param parameter - connection with model and user interface.
	 */
	public SearchTree(final PluginParameter parameter, final FBSecondPanel fbSecondPanel) {
		// create components UI
		this.view = new JPanel(new GridBagLayout());
		this.tree = new JTree();
		this.treeRenderer = new SearchTreeRenderer(null);
		new NodeManager(this.tree, fbSecondPanel, parameter);
		this.locker = new Locker("Level locker");
		this.locker.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				SearchTree.this.tree.requestFocus();
			}
		});
		// package UI
		this.tree.setCellRenderer(this.treeRenderer);
		scrollPane = new JScrollPane(this.tree);
		
		GridBagConstraints gbc = new GridBagConstraints(0,0, 1,1, 1,1, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0,0);
		this.view.add(scrollPane, gbc);
		
		gbc = new GridBagConstraints(0,1, 1,1, 1,0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0,0);
		this.view.add(this.locker.getView(), gbc);
		// level locker
		KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() {					
			public boolean dispatchKeyEvent(KeyEvent e) {
				if(SearchTree.this.locker.getStation() == Locker.DEF_LOCK && tree.isFocusOwner()) {
					if (e.getID() == KeyEvent.KEY_PRESSED && e.getKeyCode() == KeyEvent.VK_DOWN) {
						synchronized (SearchTree.this.theMutexObject) {
							if(SearchTree.this.currNode != null) {
								TreePath tp3 = SearchTree.this.tree.getSelectionPath();
								if(tp3 == null) return(false);
								TreePath tp2 = tp3.getParentPath();
								if(tp2 == null) return(false);
								TreePath tp1 = tp2.getParentPath();
								GraphTreeElement node3 = SearchTree.this.currNode.vg_getNode(tp3);
								GraphTreeElement node2 = SearchTree.this.currNode.vg_getNode(tp2);
								GraphTreeElement node1 = SearchTree.this.currNode.vg_getNode(tp1);
								boolean workDone = false;
								for(int i = 0; i < node2.getChildCount(); i++) {
									GraphTreeElement buf = (GraphTreeElement)node2.getChildAt(i);
									if(buf == node3) {
										if(i < node2.getChildCount() - 1) {
											buf = (GraphTreeElement)node2.getChildAt(i+1);
											Object[] newPath = new Object[tp2.getPathCount() + 1];
											System.arraycopy(tp2.getPath(), 0, newPath, 0, tp2.getPathCount());
											newPath[tp2.getPathCount()] = buf;
											TreePath newTP = new TreePath(newPath);
											tree.setSelectionPath(newTP);
											Rectangle aRect = tree.getPathBounds(newTP);
											if(aRect != null) {
												aRect.x = scrollPane.getHorizontalScrollBar().getValue();
												tree.scrollRectToVisible(aRect);
											}
											workDone = true;											
										}
										break;
									}
								}
								if(!workDone && node1 != null) {
									for(int i = 0; i < node1.getChildCount() - 1; i++) {
										GraphTreeElement buf = (GraphTreeElement)node1.getChildAt(i);
										if(buf == node2) {
											for(int j = i+1; j < node1.getChildCount(); j++) {
												buf = (GraphTreeElement)node1.getChildAt(j);
												if(!buf.isLeaf()) {
													GraphTreeElement bufInner = (GraphTreeElement)buf.getChildAt(0);
													Object[] newPath = new Object[tp1.getPathCount() + 2];
													System.arraycopy(tp1.getPath(), 0, newPath, 0, tp1.getPathCount());
													newPath[tp1.getPathCount()] = buf;
													newPath[tp1.getPathCount() + 1] = bufInner;
													TreePath newTP = new TreePath(newPath);
													tree.setSelectionPath(newTP);
													Rectangle aRect = tree.getPathBounds(newTP);
													if(aRect != null) {
														aRect.x = scrollPane.getHorizontalScrollBar().getValue();
														tree.scrollRectToVisible(aRect);
													}
													break;
												}
											}
											break;
										}
									}										
								}
							}
						}
						return(true);
					} else if(e.getID() == KeyEvent.KEY_PRESSED && e.getKeyCode() == KeyEvent.VK_UP) {
						synchronized (SearchTree.this.theMutexObject) {
							if(SearchTree.this.currNode != null) {
								TreePath tp3 = SearchTree.this.tree.getSelectionPath();
								if(tp3 == null) return(false);
								TreePath tp2 = tp3.getParentPath();
								if(tp2 == null) return(false);
								TreePath tp1 = tp2.getParentPath();
								GraphTreeElement node3 = SearchTree.this.currNode.vg_getNode(tp3);
								GraphTreeElement node2 = SearchTree.this.currNode.vg_getNode(tp2);
								GraphTreeElement node1 = SearchTree.this.currNode.vg_getNode(tp1);
								boolean workDone = false;
								for(int i = 0; i < node2.getChildCount(); i++) {
									GraphTreeElement buf = (GraphTreeElement)node2.getChildAt(i);
									if(buf == node3) {
										if(i > 0) {
											buf = (GraphTreeElement)node2.getChildAt(i-1);
											Object[] newPath = new Object[tp2.getPathCount() + 1];
											System.arraycopy(tp2.getPath(), 0, newPath, 0, tp2.getPathCount());
											newPath[tp2.getPathCount()] = buf;
											TreePath newTP = new TreePath(newPath);
											tree.setSelectionPath(newTP);
											Rectangle aRect = tree.getPathBounds(newTP);
											if(aRect != null) {
												aRect.x = scrollPane.getHorizontalScrollBar().getValue();
												tree.scrollRectToVisible(aRect);
											}
											workDone = true;
										}
										break;
									}
								}
								if(!workDone && node1 != null) {
									for(int i = 1; i < node1.getChildCount(); i++) {
										GraphTreeElement buf = (GraphTreeElement)node1.getChildAt(i);
										if(buf == node2) {
											for(int j = i-1; j >= 0; j--) {
												buf = (GraphTreeElement)node1.getChildAt(j);
												if(!buf.isLeaf()) {
													GraphTreeElement bufInner = (GraphTreeElement)buf.getChildAt(buf.getChildCount() - 1);
													Object[] newPath = new Object[tp1.getPathCount() + 2];
													System.arraycopy(tp1.getPath(), 0, newPath, 0, tp1.getPathCount());
													newPath[tp1.getPathCount()] = buf;
													newPath[tp1.getPathCount() + 1] = bufInner;
													TreePath newTP = new TreePath(newPath);
													tree.setSelectionPath(newTP);
													Rectangle aRect = tree.getPathBounds(newTP);
													if(aRect != null) {
														aRect.x = scrollPane.getHorizontalScrollBar().getValue();
														tree.scrollRectToVisible(aRect);
													}
													break;
												}
											}
											break;
										}
									}										
								}
							}
						}
						return(true);
					}
				}
				if(e.getID() == KeyEvent.KEY_PRESSED && e.getKeyCode() == KeyEvent.VK_SPACE) {
					SearchTree.this.locker.changeStation();
					return(true);
				}
				return(false);
			}
		});
	}
	/**
	 * Replace current root node of tree(object).
	 * @param node - new root node.
	 * @param array - array of allowed items.If null, then all items are allowed.
	 */
	public void replaceNode(final GraphTreeElement node, final Set<Integer> array) {
		synchronized (theMutexObject) {			
			this.srcNode = node;
			this.array = array;
		}
		this.setBrowseMode(this.browseMode);
	}
	/**
	 * This method returns view of this object.
	 */
	public JPanel getView() {
		return(this.view);
	}
	/**
	 * This method sets browse mode for resulting tree.
	 */
	public void setBrowseMode(int mode) {
		synchronized (theMutexObject) {
			this.browseMode = mode;
			//package view
			if(this.srcNode != null) {
				this.currNode = null;
				if(this.browseMode == SearchPluginGlobals.DEF_BROWSE_MODE_1) {
					this.currNode = this.srcNode;
				} else {
					this.currNode = this.srcNode.clone();
					this.currNode.vg_removeLeaves4Vertex(array);
				}
				this.treeRenderer.setAllowedList(this.array);
				final DefaultTreeModel newModel = new DefaultTreeModel(this.currNode);
				this.tree.setModel(newModel);
				this.tree.stopEditing();
				this.tree.updateUI();
			}
		}
	}
}
